/*************************************************************************
 Crytek Source File.
 Copyright (C), Crytek Studios, 2001-2004.
 -------------------------------------------------------------------------
 $Id$
 $DateTime$
 Description:  the part of a defence wall that every connection needs
 -------------------------------------------------------------------------
 History:
 - 10/08/2004   : Created by Craig Tiller, based on CDefenceWall by Timur
*************************************************************************/

#include "StdAfx.h"
#include "DefenceContext.h"
#include "DefenceData.h"
#include "ICryPak.h"
#include "IConsole.h"
#include "Protocol/NetChannel.h"
#include "Network.h"
#include "ITimer.h"
#include "Context/ContextView.h"
#include "Config.h"
#include "IAntiCheatManager.h"
#include "IGame.h"
#include "IGameFramework.h"

#if USE_DEFENCE

//
// probe thread
//

void CProbeThread::Run()
{
	CryThreadSetName( -1, "ServerProbe" );

	while (true)
	{
		CProbeRequest* p = NULL;
		{
			CryAutoLock<TLock> lk(m_mtx);
			while (m_incoming.empty())
				m_cond.Wait( m_mtx );
			p = m_incoming.front();
//			CryLogAlways("[Defense] CProbeThread::Run() popping (%p)", p);
			m_incoming.pop();
		}

		if (!p)
			break;

		FUNCTION_PROFILER(gEnv->pSystem, PROFILE_NETWORK);
		SDataProbeContext ctx = p->ctx;
		gEnv->pSystem->GetIDataProbe()->GetCode( ctx );
		p->codes[p->ncodes++] = ctx.nCode;

    if ( ctx.nCtxType == EDataProbeContextType_FileCheck )
    {
		  // hack so that dds files on high/low spec machines can be checked
		  string sGameFolder = PathUtil::GetGameFolder() + "/";
		  int iGameFolderLen = sGameFolder.length();
		  if (p->isServer && PathUtil::MatchWildcard(ctx.sFilename, "*.dds") && (int)ctx.sFilename.length() > iGameFolderLen && 0 == stricmp(ctx.sFilename.substr(0,iGameFolderLen).c_str(), sGameFolder.c_str()))
		  {
			  ctx = p->ctx;
			  ctx.sFilename = sGameFolder + "_server_lowspec/" + ctx.sFilename.substr(iGameFolderLen);
			  gEnv->pSystem->GetIDataProbe()->GetCode( ctx );
			  p->codes[p->ncodes++] = ctx.nCode;
		  }
    }

		p->Complete();
	}
}

void CProbeThread::Push(CProbeRequest* p)
{
	CryAutoLock<TLock> lk(m_mtx);
//	CryLogAlways("[Defense] CProbeThread::Push(%p)", p);
	m_incoming.push(p);
	m_cond.Notify();
}

void CProbeThread::AbortPendingRequests()
{
	CryAutoLock<TLock> lk(m_mtx);
	CProbeRequest* pProbeRequest = NULL;
	uint32 count = 0;

	while (!m_incoming.empty())
	{
		pProbeRequest = m_incoming.front();
		m_incoming.pop();
		++count;

		if (pProbeRequest)
		{
			pProbeRequest->Abort();
		}
	}

//	CryLogAlways("[Defense] Aborting %i probe requests", count);
	Cancel();
}

static CProbeThread& GetProbeThread()
{
	static CProbeThread * pThread = 0;
	if (!pThread)
	{
		pThread = new CProbeThread;
		pThread->Start();
	}
	else
	{
		if (!pThread->IsRunning())
		{
			pThread->Start();
		}
	}
	return *pThread;
}

//
// Common
//

class CServerDefence::CServerCheckMessage : public INetMessage
{
public:
  CServerCheckMessage( CServerDefence * pDefence, const SDataProbeContext& ctx, uint32 id, CMementoMemoryManager& mmm, const SNetMessageDef * pDef ) : INetMessage(pDef), m_pDefence(pDefence), m_ctx(ctx), m_id(id), m_mmm(mmm)
  {
    SetGroup('CHK');
    SetPriorityDelta( (cry_rand32()%10)/10.0f - 0.5f );
  }

  virtual EMessageSendResult WritePayload( TSerialize ser, uint32 a, uint32 b ) = 0;
	virtual void UpdateState( uint32 seq, ENetSendableStateUpdate state )	{}

protected:
	
	CServerDefence * m_pDefence;
	uint32 m_id;
	SDataProbeContext m_ctx;
	CMementoMemoryManager& m_mmm;
};

class CServerDefence::CServerCheckMessageFile : public CServerCheckMessage
{
public:
	static CServerCheckMessageFile * Create(CServerDefence * pDefence, const SDataProbeContext& ctx, uint32 id, CMementoMemoryManager& mmm, const SNetMessageDef * pDef )
	{
		return new (mmm.AllocPtr(sizeof(CServerCheckMessageFile))) CServerCheckMessageFile(pDefence, ctx, id, mmm, pDef);
	}

  EMessageSendResult WritePayload( TSerialize ser, uint32 a, uint32 b )
  {
    ser.Value("id", m_id);
    ser.Value("filename", m_ctx.sFilename);
    ser.Value("offset", m_ctx.nOffset);
    ser.Value("size", m_ctx.nSize);
    m_pDefence->SentRequest( m_id, m_ctx );
    return eMSR_SentOk;
  }

	size_t GetSize() { return sizeof(*this); }

private:
	CServerCheckMessageFile( CServerDefence * pDefence, const SDataProbeContext& ctx, uint32 id, CMementoMemoryManager& mmm, const SNetMessageDef * pDef ) : CServerCheckMessage(pDefence,ctx,id,mmm,pDef)
	{
	}

	void DeleteThis()
	{
		CMementoMemoryManager* pMMM = &m_mmm;
		this->~CServerCheckMessageFile();
		pMMM->FreePtr( this, sizeof(CServerCheckMessageFile) );
	}
};

class CServerDefence::CServerCheckMessageCvar : public CServerDefence::CServerCheckMessage
{
public:
  static CServerCheckMessageCvar * Create(CServerDefence * pDefence, const SDataProbeContext& ctx, uint32 id, CMementoMemoryManager& mmm, const SNetMessageDef * pDef )
  {
    return new (mmm.AllocPtr(sizeof(CServerCheckMessageCvar))) CServerCheckMessageCvar(pDefence, ctx, id, mmm, pDef);
  }

  EMessageSendResult WritePayload( TSerialize ser, uint32 a, uint32 b )
  {
    ser.Value("id", m_id);
    ser.Value("offset", m_ctx.nOffset);
    ser.Value("size", m_ctx.nSize);
    ser.Value("codeinfo", m_ctx.nCodeInfo);
    m_pDefence->SentRequest( m_id, m_ctx );
    return eMSR_SentOk;
  }

  size_t GetSize() { return sizeof(*this); }

private:
  CServerCheckMessageCvar( CServerDefence * pDefence, const SDataProbeContext& ctx, uint32 id, CMementoMemoryManager& mmm, const SNetMessageDef * pDef ) : CServerCheckMessage(pDefence,ctx,id,mmm,pDef)
  {
  }

  void DeleteThis()
  {
    CMementoMemoryManager* pMMM = &m_mmm;
    this->~CServerCheckMessageCvar();
    pMMM->FreePtr( this, sizeof(CServerCheckMessageCvar) );
  }
};

class CClientDefence::CClientReplyMessage : public INetMessage
{
public:
	static CClientReplyMessage * Create( uint32 id, uint64 nCode, CMementoMemoryManager& mmm )
	{
		return new (mmm.AllocPtr(sizeof(CClientReplyMessage))) CClientReplyMessage(id, nCode, mmm);
	}

	EMessageSendResult WritePayload( TSerialize ser, uint32 a, uint32 b )
	{
		ser.Value("id", m_id);
		ser.Value("code", m_nCode);
		return eMSR_SentOk;
	}

	void UpdateState( uint32 seq, ENetSendableStateUpdate state )
	{
	}

	size_t GetSize() { return sizeof(*this); }

private:
	CClientReplyMessage( uint32 id, uint64 nCode, CMementoMemoryManager& mmm ) : 
	  INetMessage(CServerDefence::CheckMessage), m_id(id), m_nCode(nCode), m_mmm(mmm)
	{
		SetGroup('chk');
		SetPriorityDelta( (cry_rand32()%10)/10.0f - 0.5f );
	}

	void DeleteThis()
	{
		CMementoMemoryManager * pMMM = &m_mmm;
		this->~CClientReplyMessage();
		pMMM->FreePtr( this, sizeof(CClientReplyMessage) );
	}

	uint32 m_id;
	uint64 m_nCode;
	CMementoMemoryManager& m_mmm;
};

//
// CClientDefence
//

CClientDefence::CClientDefence( CNetChannel * pParent )
{
	m_pendingRequests = 0;
	m_pParent = pParent;
	m_pMMM = pParent->GetChannelMMM();
}

class CClientDefence::CClientQuery : public CProbeRequest
{
public:
	static CClientQuery * Create( const SDataProbeContext& c, uint32 id, _smart_ptr<CNetChannel> pChannel, CMementoMemoryManager& mmm, volatile int * pRC )
	{
		return new (mmm.AllocPtr(sizeof(CClientQuery))) CClientQuery(c, id, pChannel, mmm, pRC);
	}

	void Complete()
	{
		SCOPED_GLOBAL_LOCK;
		CReleaseRefCount rrc(this);
		m_pChannel->AddSendable( CClientReplyMessage::Create(m_id, codes[0], m_mmm), 0, NULL, NULL );
		CMementoMemoryManager * pMMM = &m_mmm;
		this->~CClientQuery();
		pMMM->FreePtr(this, sizeof(CClientQuery));
	}

private:
	CClientQuery( const SDataProbeContext& c, uint32 id, _smart_ptr<CNetChannel> pChannel, CMementoMemoryManager& mmm, volatile int * pRC ) : CProbeRequest(c, false, pRC), m_id(id), m_pChannel(pChannel), m_mmm(mmm) {}

	uint32 m_id;
	_smart_ptr<CNetChannel> m_pChannel;
	CMementoMemoryManager& m_mmm;
};

NET_IMPLEMENT_IMMEDIATE_MESSAGE( CClientDefence, CheckMessageFile0, eNRT_ReliableUnordered, 0 )
{
	FUNCTION_PROFILER(gEnv->pSystem, PROFILE_NETWORK);

	uint32 id;
	SDataProbeContext ctx;
	ser.Value("id", id);
	ser.Value("filename", ctx.sFilename);
	ser.Value("offset", ctx.nOffset);
	ser.Value("size", ctx.nSize);
	ctx.nCodeInfo = DATAPROBE_CRC32;
  ctx.nCtxType = EDataProbeContextType_FileCheck;

	GetProbeThread().Push(CClientQuery::Create(ctx, id, m_pParent, *m_pMMM, &m_pendingRequests));
	
	return true;
}

NET_IMPLEMENT_IMMEDIATE_MESSAGE( CClientDefence, CheckMessageFile1, eNRT_ReliableUnordered, 0 )
{
  FUNCTION_PROFILER(gEnv->pSystem, PROFILE_NETWORK);

  uint32 id;
  SDataProbeContext ctx;
  ser.Value("id", id);
  ser.Value("filename", ctx.sFilename);
  ser.Value("offset", ctx.nOffset);
  ser.Value("size", ctx.nSize);
  ctx.nCodeInfo = DATAPROBE_CRC64;
  ctx.nCtxType = EDataProbeContextType_FileCheck;

  GetProbeThread().Push(CClientQuery::Create(ctx, id, m_pParent, *m_pMMM, &m_pendingRequests));

  return true;
}

NET_IMPLEMENT_IMMEDIATE_MESSAGE( CClientDefence, CheckMessageFile2, eNRT_ReliableUnordered, 0 )
{
  FUNCTION_PROFILER(gEnv->pSystem, PROFILE_NETWORK);

  uint32 id;
  SDataProbeContext ctx;
  ser.Value("id", id);
  ser.Value("filename", ctx.sFilename);
  ser.Value("offset", ctx.nOffset);
  ser.Value("size", ctx.nSize);
  ctx.nCodeInfo = DATAPROBE_ADLER32;
  ctx.nCtxType = EDataProbeContextType_FileCheck;

  GetProbeThread().Push(CClientQuery::Create(ctx, id, m_pParent, *m_pMMM, &m_pendingRequests));

  return true;
}

NET_IMPLEMENT_IMMEDIATE_MESSAGE( CClientDefence, CheckMessageFile3, eNRT_ReliableUnordered, 0 )
{
  FUNCTION_PROFILER(gEnv->pSystem, PROFILE_NETWORK);

  uint32 id;
  SDataProbeContext ctx;
  ser.Value("id", id);
  ser.Value("filename", ctx.sFilename);
  ser.Value("offset", ctx.nOffset);
  ser.Value("size", ctx.nSize);
  ctx.nCodeInfo = DATAPROBE_PURE_CRC32;
  ctx.nCtxType = EDataProbeContextType_FileCheck;

  GetProbeThread().Push(CClientQuery::Create(ctx, id, m_pParent, *m_pMMM, &m_pendingRequests));

  return true;
}

NET_IMPLEMENT_IMMEDIATE_MESSAGE( CClientDefence, CheckMessageFile4, eNRT_ReliableUnordered, 0 )
{
  FUNCTION_PROFILER(gEnv->pSystem, PROFILE_NETWORK);

  uint32 id;
  SDataProbeContext ctx;
  ser.Value("id", id);
  ser.Value("filename", ctx.sFilename);
  ser.Value("offset", ctx.nOffset);
  ser.Value("size", ctx.nSize);
  ctx.nCodeInfo = DATAPROBE_MD5;
  ctx.nCtxType = EDataProbeContextType_FileCheck;

  GetProbeThread().Push(CClientQuery::Create(ctx, id, m_pParent, *m_pMMM, &m_pendingRequests));

  return true;
}

NET_IMPLEMENT_IMMEDIATE_MESSAGE( CClientDefence, CheckMessageCvar, eNRT_ReliableUnordered, 0 )
{
  FUNCTION_PROFILER(gEnv->pSystem, PROFILE_NETWORK);

  uint32 id;
  SDataProbeContext ctx;
  ser.Value("id", id);
  ser.Value("start", ctx.nOffset);
  ser.Value("end", ctx.nSize);
  ser.Value("codeinfo", ctx.nCodeInfo);
  ctx.nCtxType = EDataProbeContextType_CvarCheck;

  GetProbeThread().Push(CClientQuery::Create(ctx, id, m_pParent, *m_pMMM, &m_pendingRequests));

  return true;
}

void CClientDefence::DefineProtocol( IProtocolBuilder * pBuilder )
{
	pBuilder->AddMessageSink( this, 
		CServerDefence::GetProtocolDef(), 
		CClientDefence::GetProtocolDef() );
}

void CClientDefence::AddProtectedFile( const string&, bool headerOnly )
{
}

void CClientDefence::ClearProtectedFiles()
{
}

bool CClientDefence::HasRemoteDef( const SNetMessageDef * pDef )
{
	return CServerDefence::ClassHasDef( pDef );
}

//
// CServerDefence
//


CServerDefence::CServerDefence( CNetChannel * pParent )
{






















}

CServerDefence::~CServerDefence()
{
#if defined(DEDICATED_SERVER)
	TIMER.CancelTimer( m_timer );
	TIMER.CancelTimer( m_nextSend );

	MMM_REGION(m_pMMM);
  int numAssetGroups = m_protectedLists.size();
  for ( int i = 0; i < numAssetGroups; ++i )
  {
	  m_protectedLists[i]->m_protectedFiles.reset();
  }
  m_protectedLists.erase(m_protectedLists.begin(), m_protectedLists.end());
  m_protectedLists.clear();
	m_pending.reset();
#endif //#if defined(DEDICATED_SERVER)
}

void CServerDefence::AddProtectedFile( const string& fn, bool headerOnly )
{











}

void CServerDefence::ClearProtectedFiles()
{
#if defined(DEDICATED_SERVER)
	MMM_REGION(m_pMMM);
  int assetgroups = m_protectedLists.size();
  for ( int i = 0; i < assetgroups; ++i )
  {
	  m_protectedLists[i]->m_protectedFiles->clear();
  }
#endif //#if defined(DEDICATED_SERVER)
}

void CServerDefence::SendNextRequest()
{
#if defined(DEDICATED_SERVER)
	if (!CNetCVars::Get().CheatProtectionLevel || !gEnv->bMultiplayer || m_pParent->IsSuicidal())
  {
		return;
  }

  int checktype = 0;
  if ( m_cvarCheckEnabled )
  {
    checktype = rand()%2;
  }

  switch(checktype)
  {
    case 0 :
      {
        SendNextRequestFile();
      }
      break;
    case 1 : 
      {
        SendNextRequestCVar();
      }
      break;
  }
#endif
}

void CServerDefence::SendNextRequestFile()
{

















































}

void CServerDefence::SendNextRequestCVar()
{
#if defined(DEDICATED_SERVER)
  MMM_REGION(m_pMMM);

  IDataProbe * pProbe = gEnv->pSystem->GetIDataProbe();

  int totalCvars = gEnv->pConsole->GetNumCheatVars();

  SDataProbeContext probe;
  probe.nCodeInfo = rand()%3;
  probe.nOffset = rand()%totalCvars;
  probe.nSize = rand()%(totalCvars-probe.nOffset);
  probe.nCtxType = EDataProbeContextType_CvarCheck;
  
  uint32 id = m_nextID++;
  m_pParent->NetAddSendable( CServerCheckMessageCvar::Create(this, probe, id, *m_pMMM, CClientDefence::CheckMessageCvar), 0, NULL, NULL );
#endif //#if defined(DEDICATED_SERVER)
}

void CServerDefence::SentRequest( uint32 id, const SDataProbeContext& ctx )
{
#if defined(DEDICATED_SERVER)
	MMM_REGION(m_pMMM);
	m_pending->insert(std::make_pair(id, ctx));
#endif //#if defined(DEDICATED_SERVER)
}

void CServerDefence::DefineProtocol( IProtocolBuilder * pBuilder )
{
	pBuilder->AddMessageSink( this, 
		CClientDefence::GetProtocolDef(), 
		CServerDefence::GetProtocolDef() );
}

bool CServerDefence::HasRemoteDef( const SNetMessageDef * pDef )
{
#if defined(DEDICATED_SERVER)
	return CClientDefence::ClassHasDef( pDef );
#else
  return false;
#endif //#if defined(DEDICATED_SERVER)
}

void CServerDefence::TimerCallback(NetTimerId, void* p, CTimeValue now)
{





















}

void CServerDefence::SendNextRequestCallback(NetTimerId, void* p, CTimeValue now)
{
#if defined(DEDICATED_SERVER)
	CServerDefence * pThis = static_cast<CServerDefence*>(p);
	pThis->SendNextRequest();
	pThis->m_nextSend = TIMER.AddTimer( g_time + (cry_rand32() % 10) / 10.0f, SendNextRequestCallback, p );
#endif //#if defined(DEDICATED_SERVER)
}

void CServerDefence::StartCvarRequests()
{
#if defined(DEDICATED_SERVER)
  m_cvarCheckEnabled = true;
#endif
}

void CServerDefence::EndCvarRequests()
{
#if defined(DEDICATED_SERVER)
  m_cvarCheckEnabled = false;
#endif
}

void CServerDefence::Abort()
{
	GetProbeThread().AbortPendingRequests();
}

class CServerDefence::CServerValidate : public CProbeRequest
{
public:
	static CServerValidate * Create(const SDataProbeContext& c, uint64 nCode, _smart_ptr<CNetChannel> pChannel, CMementoMemoryManager& mmm, volatile int * pRC)
	{
		return new (mmm.AllocPtr(sizeof(CServerValidate))) CServerValidate(c, nCode, pChannel, mmm, pRC);
	}

	void Complete()
	{


































		CMementoMemoryManager * pMMM = &m_mmm;
		this->~CServerValidate();
		SCOPED_GLOBAL_LOCK;
		pMMM->FreePtr(this, sizeof(CServerValidate));
	}

	void Abort()
	{
		CReleaseRefCount rrc(this);
		CMementoMemoryManager * pMMM = &m_mmm;
		this->~CServerValidate();
		SCOPED_GLOBAL_LOCK;
		pMMM->FreePtr(this, sizeof(CServerValidate));
	}

private:
	CServerValidate( const SDataProbeContext& c, uint64 nCode, _smart_ptr<CNetChannel> pChannel, CMementoMemoryManager& mmm, volatile int * pRC ) : CProbeRequest(c, true, pRC), m_nCode(nCode), m_pChannel(pChannel), m_mmm(mmm) {}

	uint64 m_nCode;
	_smart_ptr<CNetChannel> m_pChannel;
	CMementoMemoryManager& m_mmm;
};

NET_IMPLEMENT_IMMEDIATE_MESSAGE( CServerDefence, CheckMessage, eNRT_ReliableUnordered, 0 )
{
	FUNCTION_PROFILER(gEnv->pSystem, PROFILE_NETWORK);

	MMM_REGION(m_pMMM);

	uint32 id;
	ser.Value("id", id);
	TPMap::iterator it = m_pending->find(id);
	if (it == m_pending->end())
		return false;
	if ((it->second.when - g_time).GetSeconds() > (float)DATAPROBE_TIMEOUT_SECONDS)
	{
		m_pending->erase(it);
		return false;
	}
	uint64 nCode;
	ser.Value("code", nCode);

	GetProbeThread().Push(CServerValidate::Create(it->second.ctx, nCode, m_pParent, *m_pMMM, &m_pendingRequests) );
	m_pending->erase(it);

	return true;
}

#endif
